/*
	GLSL Shader abstraction
 
	MIT License

	Copyright (c) 2009, Markus Broecker <mbrckr@gmail.com>

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
 */

#include "Shader.h"
#include <vector>
#include <fstream>
#include <iostream>
#include <cstring>

using namespace glTools;

/// Local error handling code
namespace ShaderErrorHandling
{
	/// Holds a compilation or linking error line
	struct Error
	{
		Error(int line_, int code_, const std::string& msg) : file(0), line(line_), code(code_), text(msg) {}

		/*	Expects input string like "0(45) : error C1008: undefined variable "rgb" "
			TODO: change me to C++, build tokenizer!
		*/
		Error(const std::string& msg) 
		{		
			int file;
			char str[64];
			memset(str, 0, 64);
			sscanf(msg.c_str(), "%d(%d) : error C%d: %s",&file, &line, &code, str);

			size_t err_start = msg.find(str);
			this->text = msg.substr(err_start);	
		} 

		/// Prints the full debug info
		void print() const
		{
			std::cerr << "Error " << code << " at line " << line << " : " << text << std::endl;
		}

		/// Just prints the line number and error message
		void printLight() const
		{
			std::cerr << "line " << line << ": " << text << std::endl;
		}

		int file;
		int line;
		int code;
		std::string text;
	};

	/** Parses the error string from the compiler, builds a list with error 
		codes and prints them.
	*/
	static std::vector<Error> handleErrors(char* errorblock)
	{
		std::vector<Error> errors;
	
		// parse the whole error block
		char* str = strtok(errorblock, "\n");
		while (str)
		{
			errors.push_back(Error(str));
			str = strtok(0, "\n");
		}
	
		// print all errors
		for (std::vector<Error>::iterator it = errors.begin(); it != errors.end(); ++it)
			it->printLight();
		
		return errors;
	}

};


Shader::Shader( const std::string& vpFile, const std::string& fpFile)
{

	load( vpFile, fpFile );
}

Shader::~Shader()
{
	glDeleteProgram( mProgram );
	glDeleteShader( mVS );
	glDeleteShader( mFS );	
}

void Shader::bind() const
{
	glUseProgram( mProgram );
}

void Shader::disable() const
{
	glUseProgram( 0 );
}

int Shader::getUniform(const std::string& name) const
{
	return glGetUniformLocation( mProgram, name.c_str() );
}

void Shader::setUniform(const std::string& name, int i) const
{
	glUniform1i( glGetUniformLocation( mProgram, name.c_str() ), i );
}

void Shader::setUniform(const std::string& name, float f) const
{
	glUniform1f( glGetUniformLocation( mProgram, name.c_str() ), f );
}

void Shader::setUniform(const std::string& name, float x, float y, float z) const
{
	glUniform3f( glGetUniformLocation( mProgram, name.c_str() ), x, y, z );
}

void Shader::setMatrix4(const std::string& name, const float* matrix) const
{
	glUniformMatrix4fv( glGetUniformLocation(mProgram, name.c_str()), 16, GL_FALSE, matrix);
}

void Shader::setTexture2D(const std::string& name, unsigned int textureId, unsigned int textureUnit) const
{
	glActiveTexture(GL_TEXTURE0 + textureUnit);
	glBindTexture(GL_TEXTURE_2D, textureId);
	glUniform1i( glGetUniformLocation( mProgram, name.c_str() ), textureUnit );
}

void Shader::setTextureCube(const std::string& name, unsigned int texId, unsigned int unit) const
{
	glActiveTexture(GL_TEXTURE0 + unit);
	glBindTexture(GL_TEXTURE_CUBE_MAP, texId);
	glUniform1i( glGetUniformLocation( mProgram, name.c_str() ), unit );
}

bool Shader::loadShader(const std::string& fileName, GLenum type)
{
	std::vector<std::string>	contents;
	std::ifstream file;
	file.open( fileName.c_str() );
	
	if (!file.is_open())
	{
		std::cerr << "Error opening file " << fileName << "!\n";
		return false;
	}
	
	std::string line;
	while (!file.eof())
	{
		std::getline( file, line );
		line.append("\n");
		contents.push_back( line );	
	}
		
	file.close();

	int current = 0;
	if (type == GL_VERTEX_SHADER)
		current = mVS = glCreateShader( GL_VERTEX_SHADER );
	else
		current = mFS = glCreateShader( GL_FRAGMENT_SHADER );
		
	
	const char *glLines[256];
	for (unsigned int i = 0; i < contents.size(); ++i)
		glLines[i] = contents[i].c_str();
						
	glShaderSource( current, contents.size(), glLines , 0 );
	glCompileShader( current );
	
	const unsigned int BUFFER_SIZE = 512;
	char buffer[BUFFER_SIZE];
	memset( buffer, 0, BUFFER_SIZE );
	int length = 0;
	
	bool result = true;
	glGetShaderInfoLog( current, BUFFER_SIZE, &length, buffer );
	if (length > 0)
	{
		std::cerr << "Error compiling " << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader \"" << fileName << "\"." << std::endl;
		ShaderErrorHandling::handleErrors(buffer);
		result = false;
	}
	return result;
}

void Shader::load(const std::string& vpFile, const std::string& fpFile)
{
	bool compileResult0 = loadShader( vpFile, GL_VERTEX_SHADER );
	bool compileResult1 = loadShader( fpFile, GL_FRAGMENT_SHADER );
	
	if (compileResult0 && compileResult1)
	{	
		mProgram = glCreateProgram();
		glAttachShader( mProgram, mVS );
		glAttachShader( mProgram, mFS );;
		glLinkProgram( mProgram );

	
		const unsigned int BUFFER_SIZE = 512;
		char buffer[BUFFER_SIZE];
		memset( buffer, 0, BUFFER_SIZE );
		int length = 0;
	
		memset( buffer, 0, BUFFER_SIZE );
		glGetProgramInfoLog( mProgram, BUFFER_SIZE, &length, buffer );
		if (length > 0)
		{
			std::cerr << "Error linking shader!" << std::endl;
			ShaderErrorHandling::handleErrors(buffer);
		}
	
		glValidateProgram( mProgram );
		int status;
		glGetProgramiv( mProgram, GL_VALIDATE_STATUS, &status );
		if (status == GL_FALSE)
		{
			std::cerr << "Error validating shader\n";
		}
		
		std::clog << "Created shader program (" << vpFile << ", " << fpFile << ")" << std::endl;
	}
	else
	{
		std::cerr << "Unable to link shader due to compilation errors." << std::endl;
	}
}
